<?php
/**
 * Created by PhpStorm.
 * User: august
 * Date: 2021/3/31
 * Time: 15:26
 */

namespace Aug\Tools;

use Aug\Tools\traits\Singleton;

class RateLimit
{
    use Singleton;
    private $minNum = 60;   //单个用户每分钟访问数
    private $dayNum = 10000;    //单个用户每天访问数
    /**
     * @var \Redis $redis
     */
    private $redis;

    protected function __construct(\Redis $redis)
    {
        $this->redis = $redis;
    }

    public function setMinNum($minNum)
    {
        $this->minNum = $minNum;
        return $this;
    }

    public function setDayNum($dayNum)
    {
        $this->dayNum = $dayNum;
        return $this;
    }

    /**
     * 获取可用次数
     *
     * author: august 2021/4/10
     * @param $uid
     * @param $dayLimit
     * @return string
     */
    public function minLimit($uid, $minuteLimit = 0, $dayLimit = 0)
    {
        if ($minuteLimit) $this->minNum = $minuteLimit;
        if ($dayLimit) $this->dayNum = $dayLimit;

        $minNumKey = sprintf('%s_minNum', $uid);
        $dayNumKey = sprintf('%s_dayNum', $uid);
        $resMin = $this->getRedis($minNumKey, $this->minNum, 60);
        $resDay = $this->getRedis($dayNumKey, $this->dayNum, 86400);
        if (!$resMin['status'] || !$resDay['status']) {
            return join('-', [$resMin['msg'], $resDay['msg']]);
        }
        return '';
    }

    private function getRedis($key, $initNum, $expire)
    {
        $nowTime = time();
        $result = ['status' => true, 'msg' => ''];
        $redisObj = $this->redis;
        $redisObj->watch($key);
        $limitVal = $redisObj->get($key);
        if ($limitVal) {
            $limitVal = json_decode($limitVal, true);
            // 每次访问减少1个，同时根据时间,计算应该向令牌桶中添加多少个 （限定数量/有效期 * (现在时间-上次访问时间）)
            $newNum = min($initNum, ($limitVal['num'] - 1) + (($initNum / $expire) * ($nowTime - $limitVal['time'])));
            if ($newNum > 0) {
                $redisVal = json_encode(['num' => $newNum, 'time' => time()]);
            } else {
                return ['status' => false, 'msg' => '当前时刻令牌消耗完！'];
            }
        } else {
            $redisVal = json_encode(['num' => $initNum, 'time' => time()]);
        }
        $redisObj->multi();
        $redisObj->set($key, $redisVal);
        if (!$redisObj->exec()) {
            $result = ['status' => false, 'msg' => '请求频次过多'];
        }
        return $result;
    }
}